/***********************************************************************************************************************
*                                                                                                                      *
* SPLASH build system v0.1                                                                                             *
*                                                                                                                      *
* Copyright (c) 2013 Andrew D. Zonenberg                                                                               *
* All rights reserved.                                                                                                 *
*                                                                                                                      *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the     *
* following conditions are met:                                                                                        *
*                                                                                                                      *
*    * Redistributions of source code must retain the above copyright notice, this list of conditions, and the         *
*      following disclaimer.                                                                                           *
*                                                                                                                      *
*    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       *
*      following disclaimer in the documentation and/or other materials provided with the distribution.                *
*                                                                                                                      *
*    * Neither the name of the author nor the names of any contributors may be used to endorse or promote products     *
*      derived from this software without specific prior written permission.                                           *
*                                                                                                                      *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR       *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
* POSSIBILITY OF SUCH DAMAGE.                                                                                          *
*                                                                                                                      *
***********************************************************************************************************************/

#include "splashcore.h"

int g_debugVerbosity;

using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Debug output

double GetTime()
{
	timespec t;
	clock_gettime(CLOCK_REALTIME,&t);
	double d = static_cast<double>(t.tv_nsec) / 1E9f;
	d += t.tv_sec;
	return d;
}

/**
	@brief Prints a string if g_debugVerbosity is greater than or equal to the supplied debug level
 */
void DebugPrintf(int level, const char* format, ...)
{
	if(g_debugVerbosity >= level)
	{
		fprintf(stdout, "[Debug%d] ", level);
		va_list args;
		va_start(args, format);
		vfprintf(stdout, format, args);
		va_end(args);
	}
}

/**
	@brief Like DebugPrintf() but outputs a nice-looking heading
 */
void DebugPrintfHeading(int level, const char* format, ...)
{
	if(g_debugVerbosity >= level)
	{
		fprintf(stdout, "\033[32;1m[Debug%d] \033[0;36m", level);
		va_list args;
		va_start(args, format);
		vfprintf(stdout, format, args);
		va_end(args);
		fprintf(stdout, "\033[0m");
	}
}

void DebugPrintfSilent(int level, const char* format, ...)
{
	if(g_debugVerbosity >= level)
	{
		va_list args;
		va_start(args, format);
		vfprintf(stdout, format, args);
		va_end(args);
	}
}

void FatalError(const char* format, ...)
{
	fprintf(stderr, "\033[0mERROR: ");
	
	va_list args;
	va_start(args, format);
	vfprintf(stderr, format, args);
	va_end(args);
	
	//trap to debugger
	fflush(stdout);
	fflush(stderr);
	asm("int3");
	
	exit(1);
}

void FatalError(std::string str)
{
	fprintf(stderr, "\033[0mERROR: %s", str.c_str());
		
	//trap to debugger
	fflush(stdout);
	fflush(stderr);
	//asm("int3");
	
	exit(1);
}

string str_replace(const string& search, const string& replace, string subject)
{
	size_t pos = 0;
	while((pos = subject.find(search, pos)) != string::npos)
	{
		subject.replace(pos, search.length(), replace);
		pos += replace.length();
	}
	
	return subject;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Filename manipulation

/**
	@brief Canonicalizes a path name
 */
string CanonicalizePath(string fname)
{
	char* cpath = realpath(fname.c_str(), NULL);
	if(cpath == NULL)
	{
		FatalError("Could not canonicalize path %s\n", fname.c_str());
		return fname;
	}
	string str(cpath);
	free(cpath);
	return str;
}

/**
	@brief Do a chown()-to-self of the parent directory for a file to flush the NFS cache
 */
void FlushDirectoryAttributeCacheForFile(string fname)
{
	int pos = fname.rfind("/");
	string parent = fname.substr(0, pos);
	
	uid_t uid = getuid();
	struct stat buf;
	if(stat(parent.c_str(), &buf) == 0)
		uid = buf.st_uid;
	
	//forces a cache flush
	chown(parent.c_str(), uid, (gid_t) -1);
}

/**
	@brief Attempts to open a directory and returns a boolean value indicating whether it exists
 */
bool DoesDirectoryExist(string fname)
{
	//Use open/close for NFS consistency
	FlushDirectoryAttributeCacheForFile(fname);
	int hfile = open(fname.c_str(), O_RDONLY);
	if(hfile < 0)
		return false;
	bool found = true;
	struct stat buf;
	if(0 != fstat(hfile, &buf))
		found = false;
	if(!S_ISDIR(buf.st_mode))
		found = false;
	close(hfile);
	return found;
}

/**
	@brief Attempts to open a file and returns a boolean value indicating whether it exists and is readable
 */
bool DoesFileExist(string fname)
{
	//Use open/close for NFS consistency
	FlushDirectoryAttributeCacheForFile(fname);
	int hfile = open(fname.c_str(), O_RDONLY);
	if(hfile < 0)
		return false;
	close(hfile);
	return true;
}

/**
	@brief Gets the directory component of a filename
 */
string GetDirOfFile(string fname)
{
	size_t pos = fname.rfind("/");
	return fname.substr(0, pos);
}

/**
	@brief Gets the basename component of a filename
 */
string GetBasenameOfFile(string fname)
{
	size_t pos = fname.rfind("/");
	return fname.substr(pos+1);
}

string GetBasenameOfFileWithoutExt(string fname)
{
	size_t pos = fname.rfind("/");
	string base = fname.substr(pos+1);
	pos = base.rfind(".");
	return base.substr(0, pos);
}

/**
	@brief Find all files with the specified extension in a given directory.
	
	The supplied extension must include the leading dot.
 */
void FindFilesByExtension(string dir, string ext, vector<string>& files)
{
	DIR* hdir = opendir(dir.c_str());
	if(!hdir)
		FatalError("Directory %s could not be opened\n", dir.c_str());
	
	dirent ent;
	dirent* pent;
	while(0 == readdir_r(hdir, &ent, &pent))
	{
		if(pent == NULL)
			break;
		if(ent.d_name[0] == '.')
			continue;
			
		//Extension match
		string fname = CanonicalizePath(dir + "/" + ent.d_name);
		if(fname.find(ext) == (fname.length() - ext.length()) )
			files.push_back(fname);
	}
	
	//Sort the list of files to ensure determinism
	std::sort(files.begin(), files.end());
	
	closedir(hdir);
}

/**
	@brief Gets the relative path of a file within a directory.

	Good for printing out friendly file names etc during build.
 */
string GetRelativePathOfFile(string dir, string fname)
{
	size_t pos = fname.find(dir);
	if(pos == 0)
	{
		//Hit, remove it
		return fname.substr(dir.length() + 1);
	}
	return fname;
}

void MakeDirectoryRecursive(std::string path, int mode)
{
	if(!DoesDirectoryExist(path))
	{
		//Make the parent directory if needed
		string parent = GetDirOfFile(path);
		if(parent == path)
		{
			//relative path and we've hit the top, stop
			return;
		}
		if(!DoesDirectoryExist(parent))
			MakeDirectoryRecursive(parent, mode);
		
		//Make the directory
		if(0 != mkdir(path.c_str(), 0755))
		{
			//Do not fail if the directory was previously created
			if( (errno == EEXIST) && DoesDirectoryExist(path) )
			{}
			else
				FatalError("Could not create directory %s (%s)\n", path.c_str(), strerror(errno));
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Hashing

/**
	@brief Computes the SHA256 of a string and returns the hex hash
 */
string sha256(string str)
{
	unsigned char output_buf_raw[CryptoPP::SHA256::DIGESTSIZE];
	CryptoPP::SHA256().CalculateDigest(output_buf_raw, (unsigned char*)str.c_str(), str.length());
	string ret;
	for(int i=0; i<CryptoPP::SHA256::DIGESTSIZE; i++)
	{
		char buf[3];
		snprintf(buf, sizeof(buf), "%02x", output_buf_raw[i] & 0xFF);
		ret += buf;
	}
	return ret;
}

/**
	@brief Computes the SHA256 of a file's contents and returns the hex hash
 */
string sha256_file(string path)
{
	FILE* fp = fopen(path.c_str(), "rb");
	if(!fp)
		FatalError("sha256_file: Could not open file \"%s\"\n", path.c_str());
	fseek(fp, 0, SEEK_END);
	long fsize = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	char* buf = new char[fsize + 1];
	buf[fsize] = 0;
	fread(buf, 1, fsize, fp);
	fclose(fp);
	string tmp(buf);
	delete[] buf;
	return sha256(tmp);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Subprocesses

/**
	@brief Runs a shell command and returns the output
 */
string ShellCommand(string cmd, bool trimNewline)
{
	FILE* fp = popen(cmd.c_str(), "r");
	if(fp == NULL)
		FatalError(string("popen(\"") + cmd + "\") failed");
	string retval;
	char line[1024];
	while(NULL != fgets(line, sizeof(line), fp))
		retval += line;
	pclose(fp);
	
	if(trimNewline)
		retval.erase(retval.find_last_not_of(" \n\r\t")+1);
	return retval;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Library searching

/**
	@brief Search for a library given the name (m, pthread, etc)
	
	TODO: Caching so we don't do this every single build
 */
string FindSharedLibrary(string libname, string arch)
{
	vector<string> search_paths;
	if(arch == CppToolchain::CreateDefaultToolchainCached()->GetArchitecture())
	{
		//only look in system directories if we're on the system's default arch
		search_paths.push_back("/usr/local/lib");
		search_paths.push_back("/usr/lib");
	}
	search_paths.push_back(string("/usr/lib/") + arch);
	search_paths.push_back(string("/usr/") + arch + "/lib");
	
	for(size_t i=0; i<search_paths.size(); i++)
	{
		string path = string(search_paths[i]) + "/lib" + libname + ".so";
		if(DoesFileExist(path))
		{
			path = CanonicalizePath(path);
			DebugPrintfSilent(3, "    %10s (for %15s): found at %s\n", libname.c_str(), arch.c_str(), path.c_str());
			return path;
		}
	}
	
	DebugPrintfSilent(3, "    %10s (for %15s): not found\n", libname.c_str(), arch.c_str());
	return "";
}

/**
	@brief Uses pkg-config to create the compiler flags for a given package
 */
void CreateCflagsForPackage(CppCompileFlagList& flaglist, string package_name)
{
	string pkgc = ShellCommand(string("pkg-config --cflags-only-I ") + package_name);
	pkgc += " ";	//terminate with a space so search works
	
	//Parse the string
	size_t i = 0;
	while(i < pkgc.length())
	{
		//Skip everything until the -I
		i = pkgc.find("-I", i);
		if(i == string::npos)
			break;
		i += 2;
			
		//Read until the end
		size_t j = pkgc.find(" ", i);
		if(j == string::npos)
			break;
		string path = pkgc.substr(i, j-i);
		flaglist.push_back(new CppIncludeDirectoryFlag(path));
		i=j+1;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Math helpers

int gcd(int a, int b)
{
	while(b != 0)
	{
		int t = b;
		b = a % b;
		a = t;
	}
	return a;
}
